package api

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"go.uber.org/zap"
	"goshell.straysh.com/internal/cmd"
	"goshell.straysh.com/internal/helperF"
	"net/http"
	"path"
	"strings"
	"time"
)

var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
} // use default options

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

// RequestMessage RequestMessage
type RequestMessage struct {
	LogFile string `json:"log_file"`
}

func (s *WorkerService) Websocket(c *gin.Context) {
	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		s.log.Error("upgrade failed", zap.Error(err))
		return
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	conn.SetCloseHandler(func(code int, text string) error {
		message := websocket.FormatCloseMessage(code, "")
		_ = conn.WriteControl(websocket.CloseMessage, message, time.Now().Add(time.Second))
		cancel()
		return nil
	})
	user := &User{ID: 1, Name: "system"}
	s.readPump(ctx, conn, user)
	fmt.Printf("ws closed\n")
}

func (s *WorkerService) readPump(ctx context.Context, c *websocket.Conn, user *User) {
	for {
		mt, message, err := c.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure, websocket.CloseNoStatusReceived) {
				s.log.Error("ReadMessage failed", zap.Error(err))
			}
			break
		}
		s.log.Info("readPump", zap.String("recv", string(message)))

		respChan := make(chan []byte, 10)
		go func() {
			for respMsg := range respChan {
				err = c.WriteMessage(mt, respMsg)
				if err != nil {
					if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
						s.log.Error("WriteMessage failed", zap.Error(err))
					}
					break
				}
			}
			s.log.Info("stop writing ws channel -> respChan")
		}()
		var msg RequestMessage
		if err := json.Unmarshal(message, &msg); err != nil {
			respChan <- []byte(err.Error())
			close(respChan)
		} else {
			outputLogStream := func() {
				ctx2, cancel := context.WithTimeout(context.Background(), time.Hour)
				defer cancel()
				logfile := path.Join(s.config.Data.Stream.LogDir, msg.LogFile)
				args := &cmd.ArgumentStream{
					Argument: cmd.Argument{
						Script:  "tail",
						Params:  []string{"-f", "-n", "+1", logfile},
						WorkDir: s.config.Data.Stream.LogDir,
					},
					Output: make(chan string),
				}
				go func() {
					defer func() {
						s.log.Info("abort looping command channel -> args.Output")
					}()

					silent := false
					for line := range args.Output {
						if silent {
							s.log.Info("silent")
							continue
						}
						lineBytes := []byte(line)
						// 拷贝lineBytes再写入channel，避免底层slice被反复引用导致数据混乱
						t := helperF.TimeToTimeStr(time.Now()) + " "
						b := make([]byte, len(t)+len(lineBytes))
						copy(b, t)
						copy(b[len(t):], lineBytes)
						respChan <- b
						if bytes.Equal(lineBytes, []byte("___end")) {
							//close(respChan)
							cancel()
							s.log.Info("stop reading command channel -> args.Output")
							silent = true
						}
					}
				}()
				err := cmd.RunCommandStream(ctx2, args)
				if err != nil {
					if strings.Index(err.Error(), "context deadline exceeded") != -1 {
						s.log.Error("cmd.RunCommandStream abort with err", zap.Error(err))
					} else {
						s.log.Warn("cmd.RunCommandStream abort with context deadline exceeded")
					}

					respChan <- []byte(err.Error())
					close(respChan)
				}
			}
			outputLogStream()
			_ = c.Close()
			s.log.Info("tail -f work done")
		}
	}
}

//func (s *WorkerService) wsCloseHandler(c *gin.Context, conn *websocket.Conn) func(code int, text string) error {
//	return func(appData string) (err error) {
//
//	}
//}
